package iservice

import (
	"encoding/json"
	"fmt"
	"time"

	. "starsine.cn/appTpl/cmn/defs"
	"starsine.cn/appTpl/cmn/logs"
	"starsine.cn/appTpl/cmn/timermgr"
	"starsine.cn/appTpl/svccenter/registry/restful/openapi"
	"starsine.cn/appTpl/svccenter/registry/restful/ws-server/wsserver"
)

type WsService struct {
	InstanceID  string
	SerialNum   int32
	ServiceType string
	Name        string
	UserSpace   string
	Health
	HeartbeatMng
	timerID int64
	appCtxt *AppContext
	tmgr    *timermgr.TimerMgr

	data SvcData

	conn *wsserver.WsServer
}

func (t WsService) String() (str string) {
	str += fmt.Sprintln("WsService Info:")
	str += fmt.Sprintln("UserSpace: ", t.UserSpace)
	str += fmt.Sprintln("ServiceType: ", t.ServiceType)
	str += fmt.Sprintln("Name: ", t.Name)
	str += fmt.Sprintln("InstanceID: ", t.InstanceID)
	str += fmt.Sprintln("data: ", t.data)
	str += fmt.Sprintln("Health: ", t.Health)
	str += fmt.Sprintln(t.HeartbeatMng)

	str += fmt.Sprintln(t.data)

	return str
}

func CreateWsService(vType, vName, userSpace string, data SvcData, appCtxt *AppContext, tmgr *timermgr.TimerMgr) *WsService {
	p := new(WsService)

	p.InstanceID = SvcInstanceID(vType, vName, userSpace)
	p.SerialNum = 0
	p.appCtxt = appCtxt
	p.tmgr = tmgr
	p.ServiceType = vType
	p.Name = vName
	p.UserSpace = userSpace
	p.Health = HealthGood
	p.HeartbeatMng = HeartbeatMng{
		Duration:  HEARTBEAT_DURATION_SEC,
		MaxTimes:  HEARTBEAT_MAXTIMES,
		FailTimes: 0,
	}
	p.timerID = -1
	p.data = data

	logs.Debugf("service(%s-%s-%s) created", vType, vName, userSpace)
	return p
}

func (p *WsService) Start(c interface{}) {
	if conn, ok := c.(*wsserver.WsServer); ok {
		p.conn = conn
		p.heartbeatCheck()
	} else {
		logs.Errorf("WsService Start fail, input c(%v) expect type (*ws.WsServer)", c)
	}
}

func (p *WsService) End() {
	p.tmgr.CancelTimer(p.timerID)
	p.conn.Close()
}

func (p *WsService) MsgHandler(msg []byte) {
	head := openapi.WsRequestTpl{}
	err := json.Unmarshal(msg, &head)
	if err != nil {
		logs.Warnf("WsMsgHandler Unmarshal fail, err: %s", err)
		return
	}

	switch head.MsgHead.RequestType {
	case "ws-heartbeat":
		logs.Debugf("WsService.WsMsgHandler get ws-heartbeat")

	case "svc-heartbeat":
		logs.Debugf("WsService.WsMsgHandler get svc-heartbeat")
		if head.MsgHead.ResOrReq == "req" {
			p.heartbeatRequestHandle(msg, head.MsgHead)
			logs.Debugf("WsService.WsMsgHandler get svc-heartbeat request and response ok")
		} else {
			p.heartbeatResponseHandle(msg, head.MsgHead)
			logs.Debugf("WsService.WsMsgHandler get svc-heartbeat response ok")
		}

	case "svc-registry":
		logs.Warnf("WsService.WsMsgHandler get svc-registry")

	default:
		logs.Warnf("WsService.WsMsgHandler unknown request type: %s", head.MsgHead.RequestType)
	}
}

func (p *WsService) GetServiceType() string {
	return p.ServiceType
}

func (p *WsService) GetName() string {
	return p.Name
}

func (p *WsService) GetInstanceID() string {
	return p.InstanceID
}

func (p *WsService) GetUserSpace() string {
	return p.UserSpace
}

func (p *WsService) GetRpcAddr() string {
	return p.data.RpcAddr
}

func (p *WsService) GetVersion() int32 {
	return p.data.Version
}

func (p *WsService) GetHealthy() Health {
	return p.Health
}

func (p *WsService) GetSvcData() SvcData {
	return p.data
}

func (p *WsService) Send(topic string, content interface{}) error {
	// to be implement
	return nil
}

func (p *WsService) heartbeatCheck() {
	p.timerID = p.tmgr.AddPeriodTimer(
		time.Duration(p.HeartbeatMng.Duration),
		timermgr.NewOnTimeOut(p.heartbeatCheckCB),
	)

	logs.Debugf("start heartbeatCheck svc(%s) name(%s) period(%d sec)", p.InstanceID, p.Name, p.HeartbeatMng.Duration)
}

func (p *WsService) heartbeatCheckCB(params interface{}) {
	p.SerialNum++

	err := p.conn.SendMessage(&openapi.HeartbeatRequest{
		MsgHead: openapi.WsHead{
			InstanceID:  p.InstanceID,
			ResOrReq:    "req",
			RequestType: "svc-heartbeat",
			SerialNum:   p.SerialNum,
		},
	})

	if err != nil {
		logs.Warnf("heartbeat to svc: %s fail, error: %s", p.InstanceID, err)

		p.HeartbeatMng.FailTimes++
		if p.HeartbeatMng.FailTimes >= p.HeartbeatMng.MaxTimes {
			p.Health = HealthDEAD
			p.tmgr.CancelTimer(p.timerID)
			logs.Warnf("service healthy set to %s, timer id: %d canceled", p.Health, p.timerID)
		} else {
			p.Health = HealthBAD
			logs.Warnf("service healthy set to %s", p.Health)
		}
		logs.Warnf("heartbeat to %s fail count %d", p.InstanceID, p.HeartbeatMng.FailTimes)
	} else {
		logs.Debugf("heartbeat to svc(%s) ok", p.InstanceID)
	}
}

func (p *WsService) ResetHeartbeat() {
	p.HeartbeatMng.FailTimes = 0
	p.Health = HealthGood
}

// func (p *WsService) WsMsgHandler(msg []byte) {
// 	head := WsRequestTpl{}
// 	err := json.Unmarshal(msg, &head)
// 	if err != nil {
// 		logs.Warnf("WsMsgHandler Unmarshal fail, err: %s", err)
// 		return
// 	}

// 	switch head.MsgHead.RequestType {
// 	case "ws-heartbeat":
// 		logs.Debugf("WsService.WsMsgHandler get ws-heartbeat")

// 	case "svc-heartbeat":
// 		logs.Debugf("WsService.WsMsgHandler get svc-heartbeat")
// 		if head.MsgHead.ResOrReq == "req" {
// 			p.heartbeatRequestHandle(msg, head.MsgHead)
// 			logs.Debugf("WsService.WsMsgHandler get svc-heartbeat request and response ok")
// 		} else {
// 			p.heartbeatResponseHandle(msg, head.MsgHead)
// 			logs.Debugf("WsService.WsMsgHandler get svc-heartbeat response ok")
// 		}

// 	case "svc-registry":
// 		logs.Warnf("WsService.WsMsgHandler get svc-registry")

// 	default:
// 		logs.Warnf("WsService.WsMsgHandler unknown request type: %s", head.MsgHead.RequestType)
// 	}
// }

func (p *WsService) heartbeatRequestHandle(msg []byte, head openapi.WsHead) {
	data := &openapi.HeartbeatResponse{
		MsgHead: openapi.WsHead{
			InstanceID:  "sc-instance-id",
			SerialNum:   head.SerialNum,
			ResOrReq:    "res",
			RequestType: "svc-heartbeat",
			Message:     "heartbeat ok",
			Status:      "ok",
		},
	}
	if err := p.conn.SendMessage(data); err != nil {
		logs.Warnf("heartbeatRequestHandle SendMessage fail, err: %s", err)
		return
	}

	logs.Debugf("get svc-heartbeat request and response ok")
}

func (p *WsService) heartbeatResponseHandle(msg []byte, head openapi.WsHead) {
	p.ResetHeartbeat()

	logs.Debugf("get svc-heartbeat response ok")
}
